package com.qdlc.p2p.dal.mybatis.pager;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.mybatis.spring.SqlSessionTemplate;

import com.qdlc.p2p.dal.mybatis.pager.core.PageParameter;
import com.qdlc.p2p.dal.mybatis.pager.core.PageParameterWrapper;
import com.qdlc.p2p.dal.mybatis.pager.core.PaginationOperationException;

/**
 * Mybatis分页代理对象
 * <p>
 * 		该类负责代理Spring注入的Mapper接口类，用来处理Mybatis分页查询，被代理执行的方法需要有一个重载方法(带PageParameterWrapper类型的参数)，
 * 如果没有重载方法，则不能进行代理分页。
 * <code>
 * 		MapperPageProxy<UserMapper> proxy = MapperPageProxy.newBuilder(userMapper, UserMapper.class);
 * 		//使用分页代理获取分页数据集合
 * 		List<User> list = proxy.createProxy().find(String arg0, String arg1);
 * 		//获取分页信息
 *      PageParameter pageParameter = proxy.getPageParameter();
 * <code>
 * </p>
 * 
 * @author linya 2016-02-25
 */
public class PageProxyBuilder<T> {

	//代理Mybatis的Mpper接口类
	private Class<T> mapperClass;
	
	//分页参数对象
	private PageParameter pageParameter = new PageParameter();
	
	//代理方法是否被调用，只有调用代理对象后，才能获得到分页参数对象信息
	private boolean invoked = false;
	
	PageProxyBuilder(Class<T> mapperClass){
		this.mapperClass = mapperClass;
	}
	
	public static <T> PageProxyBuilder<T> newBuilder(Class<T> mapperClass){
		return new PageProxyBuilder<T>(mapperClass);
	}
	
	public PageProxyBuilder<T> setCurrentPage(int currentPage){
		this.pageParameter.setCurrentPage(currentPage);
		return this;
	}
	
	public PageProxyBuilder<T> setPageParameter(int currentPage, int pageSize){
		this.pageParameter.setCurrentPage(currentPage);
		this.pageParameter.setPageSize(pageSize);
		return this;
	}

	@SuppressWarnings("unchecked")
	public T createProxy(){
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{mapperClass}, new PagerParameterInvocationHandler());
		return (T)proxy;
	}
	
	public PageParameter getPageParameter(){
		if(!invoked){
			throw new PaginationOperationException("proxy method isn't invoked!");
		}
		return pageParameter;
	}
	
	
	private Object invokePageProxyMethod(Method method, Object[] args){
		Object parameterObject = convertArgsToSqlCommandParam(method, args);
		PageParameterWrapper parameterWrapper = PageParameterWrapper.wrap(pageParameter, parameterObject);
		Object object = invokePageProxyMethod(method, parameterWrapper);
		invoked = true;
		return object;
	}
	
	private Object invokePageProxyMethod(Method method, PageParameterWrapper parameterWrapper){
		//获取sqlId，通过sqlSessionTemplate直接查询
		String namespace = mapperClass.getName();
		String id = method.getName();
		String sqlId = namespace + "." + id;
		SqlSessionTemplate sqlSessionTemplate = MapperPageProxy.getSqlSessionTemplate();
		Object object = sqlSessionTemplate.selectList(sqlId, parameterWrapper);
		return object;
	}
	
	
	private Object convertArgsToSqlCommandParam(Method method, Object[] args){
		//如果没有参数, 返回null
		if (args == null || args.length == 0) {
			return null;
		}
		
		//解析参数上的@Param注解，并按参数位置作为key，(可能有些参数没有@Param)
		final Map<Integer, String> annoMap = new HashMap<Integer, String>();
		//标记参数中是否有@Param注解
		boolean hasParamAnnotation = false;
		//获取方法的所有参数注解
		Annotation[][] annoArrays = method.getParameterAnnotations();
		int i = 0;
		for(Annotation[] ann: annoArrays){
			boolean hasParamAnno = false;
			for(Annotation an: ann){
				if(!(an instanceof Param)){
					continue;
				}
				Param param = (Param)an;
				annoMap.put(i, param.value());
				hasParamAnno = true;
				hasParamAnnotation = true;
			}
			//对于没有@Param注解的参数，补充null值
			if(!hasParamAnno){
				annoMap.put(i, null);
			}
			i ++;
		}
		//如果只有一个参数，并且没有@Param注解，则直接返回一个参数值
		if(args.length == 1 && !hasParamAnnotation){
			return args[0];
		}
		
		//将多个参数值存放到Map中去
		final Map<String, Object> paramMap = new ParamMap<Object>();
		for(Map.Entry<Integer, String> anno: annoMap.entrySet()){
			String annoParam = anno.getValue();
			int index = anno.getKey();
			if(annoParam != null){
				paramMap.put(annoParam, args[index]);
			}
			String genericParamName = "param" + (index + 1);
			if(!paramMap.containsKey(genericParamName)){
				paramMap.put(genericParamName, args[index]);
			}
		}
		
		return paramMap;
	}
	
	private class PagerParameterInvocationHandler implements InvocationHandler{
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			return invokePageProxyMethod(method, args);
		}
	}
	
}
